package pers.cz.netty.handler;

import io.netty.channel.*;
import io.netty.util.concurrent.ScheduledFuture;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pers.cz.config.JefConfiguration;
import pers.cz.netty.client.NettyClient;
import pers.cz.netty.client.TcpNettyClient;
import pers.cz.netty.config.NettyConfigItem;
import pers.cz.netty.protocal.Message;
import pers.cz.netty.protocal.MsgType;
import pers.cz.netty.protocal.PingMsg;
import pers.cz.netty.retry.DefaultRetryPolicyImpl;
import pers.cz.netty.retry.RetryPolicy;

import java.util.concurrent.TimeUnit;

/**
 * @program: PostGirl-panent
 * @description: 心跳检测
 * @author: Cheng Zhi
 * @create: 2022-10-04 15:18
 **/
@ChannelHandler.Sharable
public class HeartBeatHandler extends SimpleChannelInboundHandler<Message> {

    private static final Logger log = LoggerFactory.getLogger(HeartBeatHandler.class);
    //private static final java.util.logging.Logger log = LogUtil.getLogger(HeartBeatHandler.class);

    /**
     * 重连次数
     */
    private int retries = 0;

    private RetryPolicy retryPolicy;

    private NettyClient nettyClient;

    public HeartBeatHandler(NettyClient nettyClient) {
        retryPolicy = new DefaultRetryPolicyImpl();
        this.nettyClient = nettyClient;
    }
    /**
     * 接收到消息时触发
     * @param channelHandlerContext
     * @param message
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, Message message) throws Exception {

        // 客户端接收到服务端回复的消息, 这里只处理心跳相关的消息，其他消息在新类中处理
        if (MsgType.PONG == message.getMsgType()) {
            log.debug("服务器回复： 消息类型：{}, 消息长度：{}, 消息体：{}", message.getMsgType(), message.getContentLength(), message.getContent());


        }
    }

    /**
     * 异常时触发
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 当Channel已经断开的情况下, 仍然发送数据, 会抛异常, 该方法会被调用.
        cause.printStackTrace();
        ctx.close();
    }

    /**
     * 建立连接时触发
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 客户端请求建立连接成功
        super.channelActive(ctx);
        // 发送ping
        sendPing(ctx.channel());
        // 连接成功重置连接次数
        retries = 0;
    }

    /**
     * 连接断开时触发
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {

        // 触发重连
        if (retries == 0) {
            // 连接即将断开
            log.info("================================================================================");
            log.info("==     ~~~   jef netty client connection is disconnecting    ~~~         ==");
            log.info("================================================================================");

            // 先主动断开，后面再重连
            ctx.disconnect();
            ctx.close();
        }
        retries = retries + 1;
        retry(ctx);

        super.channelInactive(ctx);
    }

    /**
     * 发送心跳包给服务端
     * @param channel
     */
    private void sendPing(Channel channel) {

        ScheduledFuture<?> future = channel.eventLoop().schedule(new Runnable() {
            @Override
            public void run() {
                Message message = new PingMsg();
                channel.writeAndFlush(message)
                        .addListener(f -> {
                            log.info("发送心跳包" + (f.isSuccess() ? "成功" : "失败"));
                            if (f.isSuccess()) {
                                sendPing(channel);
                            }
                        });
            }
        }, JefConfiguration.getLong(NettyConfigItem.NETTY_HEART_INTERVAL,10L), TimeUnit.SECONDS);
    }

    private void retry(ChannelHandlerContext ctx) {
        // 1、判断是否需要重连
        boolean allowRetry = retryPolicy.allowRetry(retries);

        if (allowRetry) {

            long sleepSecond = retryPolicy.retryIntervalue(retries);
            log.debug("Try connecting to the server for {} time in {} s", retries, sleepSecond);

            final EventLoop eventLoop = ctx.channel().eventLoop();
            eventLoop.schedule(() -> {
                nettyClient.connect();
            }, sleepSecond, TimeUnit.SECONDS);
            ctx.fireChannelInactive();
        } else {
            log.debug("Unable to connect after {} attempts, client will close soon ...", retries - 1);
            nettyClient.shutDown();
        }
    }

}
